#include "Chart.h"

#include <QtGui>

QT_CHARTS_USE_NAMESPACE

namespace
{

void updateChartTheme(QtCharts::QChart *chart);

}


QValueAxis *Chart::getXAxis(QtCharts::QChart *chart)
{
    return qobject_cast<QValueAxis *>(chart->axes(Qt::Horizontal).first());
}

QValueAxis *Chart::getYAxis(QtCharts::QChart *chart)
{
    return qobject_cast<QValueAxis *>(chart->axes(Qt::Vertical).first());
}

Chart::Range::Range(qreal min, qreal max)
    : min(std::min(min, max))
    , max(std::max(min, max))
{}

Chart::Ranges::Ranges(qreal x_min, qreal x_max, qreal y_min, qreal y_max)
    : x(x_min, x_max)
    , y(y_min, y_max)
{}


Chart::Chart()
    : QtCharts::QChart(QtCharts::QChart::ChartTypeCartesian, nullptr, {})
{
    addAxis(new QValueAxis, Qt::AlignBottom);
    addAxis(new QValueAxis, Qt::AlignLeft);
    initFlexible({});
}

void Chart::initFlexible(const QString title)
{
    _reset(title, {0, 1, 0, 1});
    _mode = Mode::Flexible;
    _r.reset();
}

void Chart::initFixed(const QString title, const Ranges r)
{
    setView({0, 1, 0, 1});
    _reset(title, r);
    _mode = Mode::Fixed;
    _r = r;
}

void Chart::setView(const Ranges &r)
{
    if (!r.is_valid()) return;

    auto x_ax = getXAxis(this);
    auto y_ax = getYAxis(this);

    constexpr auto double_equals = [](const double lhs, const double rhs) -> bool
    {
        if (qAbs(lhs) <= 0 || qAbs(rhs) <= 0) return qFuzzyIsNull(lhs - rhs);
        return qFuzzyCompare(lhs, rhs);
    };

    if (!double_equals(x_ax->min(), r.x.min)) x_ax->setMin(r.x.min);
    if (!double_equals(x_ax->max(), r.x.max)) x_ax->setMax(r.x.max);
    if (!double_equals(y_ax->min(), r.y.min)) y_ax->setMin(r.y.min);
    if (!double_equals(y_ax->max(), r.y.max)) y_ax->setMax(r.y.max);
}

Chart::Mode Chart::mode()
{
    return _mode;
}

void Chart::replaceSeries(const QString series_name, Style series_style)
{
    if (_serieses.contains(series_name))
    {
        auto series = _serieses[series_name].series;
        removeSeries(series);
        series->deleteLater();

        _serieses.erase(_serieses.find(series_name));
    }

    QtCharts::QXYSeries * s = nullptr;
    switch(series_style)
    {
    case Style::Spline:
        s = new QtCharts::QSplineSeries;
        break;
    case Style::Scatter:
        s = new QtCharts::QScatterSeries;
        break;
    case Style::Line:
        s = new QtCharts::QLineSeries;
        break;
    }

    s->setName(series_name);
    s->setUseOpenGL(_use_opengl);

    QChart::addSeries(s);
    s->attachAxis(getXAxis(this));
    s->attachAxis(getYAxis(this));

    _serieses[series_name].series = s;
    _serieses[series_name].is_need_update = false;
}

void Chart::setUseOpenGL(bool use)
{
    // @note Если включать OpenGL, то нужно поставить на паузу
    // моделирование и дождаться конца "событийного шторма",
    // иначе всё зависнет.
    // Предположительно, создание OpenGL виджета не совместимо
    // с забитым циклом событий
    // Предположительно необходимо один раз для каждого объекта chart
    // дождаться завершения создания виджета и потом спокойно использовать
    // во время моделирования
    _use_opengl = use;
    setAnimationOptions(_use_opengl ? QChart::NoAnimation : QChart::AllAnimations);
}

void Chart::addPoints(const QString series_name, const QVector<QPointF> points)
{
    if (points.empty())
    {
        return;
    }

    if (!_serieses.contains(series_name)) replaceSeries(series_name);

    _serieses[series_name].is_need_update = true;
    _serieses[series_name].points.append(points);

    if (_mode == Mode::Fixed)
    {
        return;
    }

    if (!_r.has_value())
    {
        _r = {
            points.first().x(), points.first().x()
            , points.first().y(), points.first().y()
        };
    }
    for (const auto &point : points)
    {
        _r->x.update(point.x());
        _r->y.update(point.y());
    }
}

void Chart::updateSeries()
{
    for (auto it = _serieses.begin(); it != _serieses.end(); ++it)
    {
        auto series = it->series;

        bool need_use_opengl_update = series->useOpenGL() != _use_opengl;
        if (!it->is_need_update && !need_use_opengl_update) continue;

        it->series->replace(it->points);
        if (need_use_opengl_update) series->setUseOpenGL(_use_opengl);

        it->is_need_update = false;
    }
}

void Chart::fitBounds()
{
    if (!_r.has_value())
    {
        return;
    }

    setView(*_r);
}


bool Chart::event(QEvent *event)
{
    if (event->type() == QEvent::PaletteChange)
    {
        ::updateChartTheme(this);
    }

    return QChart::event(event);
}

void Chart::_reset(const QString title, const Ranges r)
{
    setAnimationOptions(QChart::NoAnimation);

    setTitle(title);
    ::updateChartTheme(this);

    layout()->setContentsMargins(0, 0, 0, 0);
    setBackgroundRoundness(0);

    _serieses.clear();
    removeAllSeries();
    setView(r);

    _use_opengl = true;
    emit useOpenGLChanged(_use_opengl);
}

namespace
{

void updateChartTheme(QtCharts::QChart *chart)
{
    QPalette palette = QGuiApplication::palette();
    QtCharts::QChart::ChartTheme theme;
    if (palette.window().color().toCmyk().black() > 128)
    {
        theme = QtCharts::QChart::ChartThemeDark;
    }
    else
    {
        theme = QtCharts::QChart::ChartThemeLight;
    }
    chart->setTheme(theme);
}

}
